// 编译工具类
const compileUtil = {
    /**
     * 处理 v-text 指令
     * @param {*} node 当前元素节点
     * @param {*} expr 表达式 msg vue.js中MVVM的实现原理
     * @param {*} vm 当前vm实例
     */
    text(node,expr,vm){
        let value;
        // 当前传入的表达式expr可能是$data中的一个属性，也可能是$data中对象.属性的形式，也可能是{{}}插值表达式
        // 如果当前传入的是插值表达式
        if(expr.indexOf('{{') !== -1){
            value = expr.replace(/\{\{(.+?)\}\}/g,(...args)=>{
                // console.log(args);//["{{msg}}", "msg", 0, "{{msg}}"]
                // 添加订阅者，对数据进行监听，数据如果发生了变化，调用updater进行更新视图
                new Watcher(vm,args[1],() => {
                    // 获取到新值之后，更新视图
                    this.updater.textUpdater(node,this.getContentVal(expr,vm));
                })
                return this.getVal(args[1],vm)
            })
        }else{
            value = this.getVal(expr,vm);
        }
        // 初始化视图
        this.updater.textUpdater(node,value);
    },
    /**
     * 处理 v-html 指令
     * @param {*} node 当前元素节点
     * @param {*} expr 表达式
     * @param {*} vm 当前vm实例
     */
    html(node,expr,vm){
        const value = this.getVal(expr,vm);
        // 添加订阅者，对数据进行监听，数据如果发生了变化，调用updater进行更新视图
        new Watcher(vm,expr,(newVal) => {
            // 获取到新值之后，更新视图
            this.updater.htmlUpdater(node,newVal);
        })
        // 初始化视图
        this.updater.htmlUpdater(node,value);
    },
    /**
     * 处理 v-model 指令
     * @param {*} node 当前元素节点
     * @param {*} expr 表达式
     * @param {*} vm 当前vm实例
     */
    model(node,expr,vm){
        const value = this.getVal(expr,vm);
        // 添加订阅者，对数据进行监听，数据如果发生了变化，调用updater进行更新视图，数据驱动视图
        new Watcher(vm,expr,(newVal) => {
            // 获取到新值之后，更新视图
            this.updater.modelUpdater(node,newVal);
        })
        // 视图影响数据
        node.addEventListener('input',(e) => {
            // 拿到输入框的新值之后，去影响视图
            this.setVal(expr,vm,e.target.value)
        })
        // 初始化视图
        this.updater.modelUpdater(node,value);
    },
    // 获取到新值之后，将新值保存到vm的模型数据中
    setVal(expr,vm,inputNewVal){
        return expr.split('.').reduce((data,currentVal)=>{
            // 旧值替换成新值
            data[currentVal] = inputNewVal;
        },vm.$data);
    },
    /**
     * 处理 v-on 指令
     * @param {*} node 当前元素节点
     * @param {*} expr 表达式
     * @param {*} vm 当前vm实例
     * @param {*} eventName 当前事件名称
     */
    on(node,expr,vm,eventName){
        // 取出methods中的函数
        let fn = vm.$options.methods && vm.$options.methods[expr];
        // 调用函数时，同时改变this指向当前的vm实例
        node.addEventListener(eventName,fn.bind(vm),false);
    },
    /**
     * 处理 v-bind 指令
     * @param {*} node 当前元素节点
     * @param {*} expr 表达式
     * @param {*} vm 当前vm实例
     * @param {*} eventName 当前事件名称
     */
    bind(node,expr,vm,attrName){
        const value = this.getVal(expr,vm);
        // 添加订阅者，对数据进行监听，数据如果发生了变化，调用updater进行更新视图
        new Watcher(vm,expr,(newVal) => {
            console.log(newVal,'newValnewValnewValnewValnewValnewValnewValnewValnewValnewValnewValnewVal')
            // 获取到新值之后，更新视图
            this.updater.bindUpdater(node,newVal,attrName);
        })
        // 初始化视图
        this.updater.bindUpdater(node,value,attrName);
    },
    // 更新的函数
    updater:{
        textUpdater(node,value){
            node.textContent = value;
        },
        htmlUpdater(node,value){
            node.innerHTML = value;
        },
        modelUpdater(node,value){
            node.value = value;
        },
        bindUpdater(node,value,attrName){
            node.setAttribute(attrName,value)
        },
    },
    // 根据表达式expr从data中获取值
    getVal(expr,vm){
        // 当前传入的表达式expr可能是$data中的一个属性，也可能是$data中对象.属性的形式
        // 可以获取data中属性值，也可以获取data中对象的属性值
        return expr.split('.').reduce((data,currentVal)=>{
            return data[currentVal];
        },vm.$data);
    },
    // 对{{}}进行二次处理
    getContentVal(expr,vm){
        return expr.replace(/\{\{(.+?)\}\}/g,(...args)=>{
            return this.getVal(args[1],vm)
        })
    },
}

// 指令解析器
class Compile{
    constructor(el,vm){
        // 当前传入的el是一个元素节点则赋值给当前类的el，否则自行获取元素节点
        this.el = this.isElementNode(el) ? el : document.querySelector(el);
        this.vm = vm;
        /* 需要对根节点下的每一个节点进行编译，然后将页面中的数据(例如{{person.name}})进行替换
                频繁的编译和替换会导致页面的回流和重绘，会影响页面的性能
                文档碎片的作用：将替换之后的内容放到缓存中，需要使用时会进行获取
        */
        // 1.获取文档碎片对象，会减少页面的回流和重绘
        const fragment = this.node2Fragment(this.el);

        // 2.将文档碎片对象作为模板进行编译
        this.compile(fragment);

        // 3.将文档碎片追加到根元素中
        this.el.appendChild(fragment)
        
    }
    // 编译模板：获取到的文档碎片内容
    /** 内容如下：
     * <h2>{{person.name}}--{{person.age}}</h2>
        <h3>{{person.fav}}</h3>
        <ul>
            <li>1</li>
            <li>2</li>
            <li>3</li>
        </ul>
        <h3>{{msg}}</h3>
        <div v-text="msg"></div>
        <div v-html="msg"></div>
        <input type="text" v-model="msg">
     */
    compile(fragment){
        // 1.获取子节点
        const childNodes = fragment.childNodes;
        [...childNodes].forEach(child => {
            // 元素节点
            if(this.isElementNode(child)){
                // 编译元素节点
                this.compileElement(child)
            // 文本节点
            }else{
                // 编译文本节点 主要处理 {{}} 形式的表达式
                this.compileText(child)
            }
            // 递归遍历
            if(child.childNodes && child.childNodes.length){
                this.compile(child)
            }
        })
    }
    // 编译元素节点
    compileElement(node){
        // console.log(node);//<div v-text='msg'></div>
        // 获取属性节点
        const attributes = node.attributes;
        // console.log(attributes);//{0: v-on:click, v-on:click: v-on:click, length: 1}
        [...attributes].forEach(attr => {
            console.log(attr.name,'name') // v-text  v-on
            console.log(attr.value,'value') // msg  handleClick
            const {name,value} = attr;
            // 如果当前属性名是否是自定义指令 name的值可能是：v-text v-html v-model v-on:click
            if(this.isDirective(name)){
                console.log('==============')
                const [,dirctive] = name.split('-'); // dirctive的值可能是： text html model on:click bind
                const [dirName,eventName] = dirctive.split(':'); // dirName的值可能是：text html model on bind
                // 根据dirName调用compileUtil对象中的对应方法,用来更新数据，体现了数据驱动视图。
                compileUtil[dirName](node,value,this.vm,eventName);

                // 删除有指令的标签上的属性 将v-text等从标签中去除
                node.removeAttribute('v-' + dirctive);
            }else if(this.isEventName(name)){ // 处理以@开头的事件绑定
                let [,eventName] = name.split('@');
                compileUtil['on'](node,value,this.vm,eventName);
            }else if(this.isAttrName(name)){ // 处理以:开头的属性绑定
                let [,attrName] = name.split(':');
                compileUtil['bind'](node,value,this.vm,attrName);
            }
        });
    }
    // 编译文本节点:主要处理 {{}} 形式的表达式 ,{{}} 实现原理和v-text一样，都是用的node.textContent
    compileText(node){
        // 取出节点中的文本内容，包括换行、空格等
        const content = node.textContent;
        // 正则匹配出含{{}}的内容
        if(/\{\{(.+?)\}\}/.test(content)){
            // 调用
            compileUtil['text'](node,content,this.vm);
        }
    }
    // 判断当前属性名是否是自定义指令
    isDirective(attrName){
        return attrName.startsWith('v-');
    }
    // 判断是否是一个事件名称
    isEventName(attrName){
        return attrName.startsWith('@');
    }
    // 判断是否是一个事件名称
    isAttrName(attrName){
        return attrName.startsWith(':');
    }
    // 创建文档碎片对象
    node2Fragment(el){
        // 创建文档碎片对象
        const f = document.createDocumentFragment();
        let firstChild;
        // 遍历传入的DOM节点
        while(firstChild = el.firstChild){
            // 追加文档碎片
            f.appendChild(firstChild);
        }
        return f;
    }
    // 判断当前传入的是否是元素节点
    isElementNode(node){
        // DOM对象的nodeType属性
        // 元素节点的nodeType为1
        // 属性节点的nodeType为2
        // 文本节点的nodeType为3，文本节点包含文字、空格、换行等。
        return node.nodeType === 1;
    }
}

// 入口类
class Myvue{
    constructor(options){
        this.$el = options.el;
        this.$data = options.data;
        this.$options = options;
        if(this.$el){
            // 1.实现数据观察者
            new Observer(this.$data)
            // 2.实现指令的解析器
            new Compile(this.$el,this)
            // 使用this代理this.$data
            this.proxyData(this.$data)
        }
    }
    // 使用this代理this.$data
    proxyData(data){
        for(const key in data){
            Object.defineProperty(this,key,{
                get(){
                    return data[key];
                },
                set(newVal){
                    data[key] = newVal;
                }
            })
        }
    }
}